package cn.icanci.loopstack.rec.admin.biz.service.impl;

import cn.hutool.json.JSONUtil;
import cn.icanci.loopstack.rec.admin.biz.mapper.config.DataSourceMapper;
import cn.icanci.loopstack.rec.admin.biz.service.DataSourceService;
import cn.icanci.loopstack.rec.admin.biz.event.log.LogEvent;
import cn.icanci.loopstack.rec.admin.biz.model.DataSourceDebugResult;
import cn.icanci.loopstack.rec.admin.dal.mongodb.common.PageList;
import cn.icanci.loopstack.rec.admin.dal.mongodb.daointerface.DataSourceDAO;
import cn.icanci.loopstack.rec.admin.dal.mongodb.dateobject.DataSourceDO;
import cn.icanci.loopstack.rec.common.enums.DataSourceTypeEnum;
import cn.icanci.loopstack.rec.common.enums.LogOperatorTypeEnum;
import cn.icanci.loopstack.rec.common.enums.ModuleTypeEnum;
import cn.icanci.loopstack.rec.common.model.TextValue;
import cn.icanci.loopstack.rec.common.model.config.DataSourceVO;
import cn.icanci.loopstack.rec.engine.script.RecScriptEngine;
import cn.icanci.loopstack.rec.engine.script.RecScriptEngineManager;
import cn.icanci.loopstack.rec.engine.script.context.RecScriptEngineContext;
import cn.icanci.loopstack.rec.engine.script.wrapper.HttpResponseWrapper;
import cn.icanci.loopstack.rec.spi.event.EventDispatcher;

import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

import javax.annotation.Resource;

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONException;
import com.google.common.collect.Lists;

/**
 * @author icanci
 * @since 1.0 Created in 2022/11/12 09:04
 */
@Service
public class DataSourceServiceImpl implements DataSourceService {
    @Resource
    private DataSourceDAO    dataSourceDAO;
    @Resource
    private DataSourceMapper dataSourceMapper;
    @Resource
    private EventDispatcher  eventDispatcher;

    private final RecScriptEngine recScriptEngine         = RecScriptEngineManager.getRecScriptEngine();

    /** label 格式化 */
    protected static final String DELETED_TYPE_FORMAT     = "[无效] [%s] %s";

    /** label 格式化 */
    protected static final String NOT_DELETED_TYPE_FORMAT = "[有效] [%s] %s";

    @Override
    public List<DataSourceVO> queryAll() {
        return dataSourceMapper.dos2vos(dataSourceDAO.queryAll());
    }

    @Override
    public void save(DataSourceVO dataSource) {
        if (doInsert(dataSource)) {
            DataSourceDO insert = dataSourceMapper.vo2do(dataSource);
            dataSourceDAO.insert(insert);
            eventDispatcher.fire(new LogEvent(insert.getUuid(), ModuleTypeEnum.REC_DATA_SOURCE, JSONUtil.toJsonStr(insert), LogOperatorTypeEnum.CREATE));
        } else {
            dataSourceDAO.update(dataSourceMapper.vo2do(dataSource));
            eventDispatcher.fire(new LogEvent(dataSource.getUuid(), ModuleTypeEnum.REC_DATA_SOURCE, JSONUtil.toJsonStr(dataSource), LogOperatorTypeEnum.UPDATE));
        }
    }

    @Override
    public DataSourceVO queryById(String id) {
        return dataSourceMapper.do2vo(dataSourceDAO.queryOneById(id));
    }

    @Override
    public PageList<DataSourceVO> queryPage(DataSourceVO dataSource, int pageNum, int pageSize) {
        PageList<DataSourceDO> pageQuery = dataSourceDAO.pageQuery(dataSourceMapper.vo2do(dataSource), pageNum, pageSize);
        return new PageList<>(dataSourceMapper.dos2vos(pageQuery.getData()), pageQuery.getPaginator());
    }

    @Override
    public DataSourceVO queryByDataSourceName(String domainCode, String dataSourceName) {
        return dataSourceMapper.do2vo(dataSourceDAO.queryByDataSourceName(domainCode, dataSourceName));
    }

    @Override
    public List<TextValue> loadSelector(String domainCode) {
        List<DataSourceVO> dataSources = dataSourceMapper.dos2vos(dataSourceDAO.queryByDomainCode(domainCode));
        if (CollectionUtils.isEmpty(dataSources)) {
            return Lists.newArrayList();
        }
        List<TextValue> textValues = Lists.newArrayList();
        for (DataSourceVO dataSource : dataSources) {
            String label;
            DataSourceTypeEnum dataSourceType = dataSource.getDataSourceType();
            if (isDeleted(dataSource)) {
                label = String.format(DELETED_TYPE_FORMAT, dataSourceType.getDesc(), dataSource.getDataSourceName());
            } else {
                label = String.format(NOT_DELETED_TYPE_FORMAT, dataSourceType.getDesc(), dataSource.getDataSourceName());
            }
            String value = dataSource.getUuid();
            textValues.add(new TextValue(label, value, dataSource.getDataSourceType().getCode()));
        }
        return textValues;
    }

    @Override
    public DataSourceDebugResult debug(DataSourceVO dataSource) {
        DataSourceDebugResult result = new DataSourceDebugResult();
        result.setDataSourceType(dataSource.getDataSourceType().getDesc());

        try {
            DataSourceTypeEnum dataSourceType = dataSource.getDataSourceType();
            switch (dataSourceType) {
                case SCRIPT:
                    DataSourceVO.ScriptInfo scriptInfo = dataSource.getScriptInfo();
                    result.setScriptType(scriptInfo.getScriptType().getDesc());
                    // 脚本执行
                    RecScriptEngineContext<Object> context = scriptExecutor(scriptInfo);
                    Object realRetVal = context.getRealRetVal();
                    if (context.isSuccess()) {
                        if (realRetVal instanceof Integer || realRetVal instanceof Double || realRetVal instanceof Long) {
                            result.setSuccess(false);
                            result.setExceptionMessage("脚本执行返回类型为：" + realRetVal.getClass().getName());
                        } else if (realRetVal instanceof String) {
                            JSONUtil.toBean(String.valueOf(realRetVal), Map.class);
                            result.setSuccess(true);
                            result.setRealResult(context.getRealRetVal());
                        } else if (realRetVal instanceof Map) {
                            result.setSuccess(true);
                            result.setRealResult(JSONUtil.toJsonStr(context.getRealRetVal()));
                        } else {
                            result.setSuccess(false);
                            result.setExceptionMessage("脚本执行返回类型为：" + realRetVal.getClass().getName());
                        }
                    } else {
                        result.setSuccess(false);
                        result.setExceptionMessage(context.getThrowable().getMessage());
                    }
                    return result;
                case HTTP:
                    DataSourceVO.HttpInfo httpInfo = dataSource.getHttpInfo();
                    HttpResponseWrapper wrapper = recScriptEngine.httpEval(httpInfo.getHttpRequestType(), httpInfo.getReqUrl(), httpInfo.getReqParam(), httpInfo.getTimeout());
                    if (wrapper.isSuccess()) {
                        result.setSuccess(true);
                        result.setRealResult(wrapper.getResponse());
                    } else {
                        result.setSuccess(false);
                        result.setExceptionMessage(wrapper.getException().getMessage());
                    }
                    return result;
                default:
                    // no op
            }

            return result;
        } catch (Throwable e) {
            result.setSuccess(false);
            if (e instanceof JSONException) {
                result.setExceptionMessage("Json解析异常，脚本执行返回结果不为Map格式：" + e.getMessage());
            } else {
                result.setExceptionMessage(e.getMessage());
            }
            return result;
        }
    }

    @Override
    public DataSourceVO queryByUuid(String dataSourceUuid) {
        return dataSourceMapper.do2vo(dataSourceDAO.queryByUuid(dataSourceUuid));
    }

    /**
     * 脚本执行处理
     *
     * @param scriptInfo 执行脚本相关信息
     * @throws InterruptedException
     * @throws ExecutionException
     * @throws TimeoutException
     * @return RecScriptEngineContext
     */
    private RecScriptEngineContext<Object> scriptExecutor(DataSourceVO.ScriptInfo scriptInfo) throws InterruptedException, ExecutionException, TimeoutException {
        FutureTask<RecScriptEngineContext<Object>> task = new FutureTask<>(new ScriptExecutor(scriptInfo, recScriptEngine));
        // 启动计算：测试使用单独线程、运行时应创建线程池
        new Thread(task).start();
        // 等待执行结果
        return task.get(scriptInfo.getTimeout(), TimeUnit.SECONDS);
    }

    /** 执行器  */
    private static class ScriptExecutor implements Callable<RecScriptEngineContext<Object>> {
        /** scriptInfo */
        private final DataSourceVO.ScriptInfo scriptInfo;
        /** recScriptEngine执行引擎 */
        private final RecScriptEngine         recScriptEngine;

        public ScriptExecutor(DataSourceVO.ScriptInfo scriptInfo, RecScriptEngine recScriptEngine) {
            this.scriptInfo = scriptInfo;
            this.recScriptEngine = recScriptEngine;
        }

        @Override
        public RecScriptEngineContext<Object> call() throws Exception {
            return recScriptEngine.eval(scriptInfo.getScriptType(), scriptInfo.getScriptContent());
        }
    }
}
